#include <fstream>
#include <simulator/elf/elf32/elf32Loader.h>
#include <simulator/report/report.h>
#include <simulator/memory/segment.h>
#include <simulator/memory/memory.h>
using namespace std;

u64 ELF32Loader::loadELFProgramIntoMemory(const char *elfFilePath, std::shared_ptr<Memory> memory) {
    ifstream elfFile;
    // 以二进制只读方式打开elf文件
    elfFile.open(elfFilePath, ios::in | ios::binary);
    // 如果打开文件失败，报告错误
    if (!elfFile)
        fatalMacro("elf文件打开失败！");

    // 读取ELF Header
    char buffer[sizeof(Elf32_Ehdr)];
    elfFile.read(buffer, sizeof(Elf32_Ehdr));
    auto *ehdr = reinterpret_cast<Elf32_Ehdr *>(buffer);

    // 确保ELF文件魔数正确
    if (*(u32 *) ehdr != *(u32 *) ELFMAG)
        fatalMacro("elf文件格式不正确！");

    // 读取elf program header table
    Elf32_Phdr phdr;
    for (u16 i = 0; i < ehdr->e_phnum; i++) {
        // 加载一个程序头部描述符
        load_phdr(&phdr, ehdr, i, elfFile);
        if (phdr.p_type == PT_LOAD) {
            loadSegmentIntoMemory(i, memory, &phdr, elfFile);
        }
    }
    return ehdr->e_entry;
}

void ELF32Loader::load_phdr(Elf32_Phdr *phdr, Elf32_Ehdr *ehdr, u32 i, std::ifstream &elfFile) {
    // 将elf文件流对象的读起始位置设置为program header table的第i项
    elfFile.seekg(ehdr->e_phoff + ehdr->e_phentsize * i, std::ifstream::beg);
    // 读取elf第i个用户程序头部段的数据到phdr中
    elfFile.read(reinterpret_cast<char *>(phdr), ehdr->e_phentsize);
}

void
ELF32Loader::loadSegmentIntoMemory(u64 index, std::shared_ptr<Memory> memory, Elf32_Phdr *phdr, ifstream &elfFile) {
    // 获取从文件开始到该段开头的第一个字节的偏移
    u32 offset = phdr->p_offset;
    // 获取该段第一个字节在内存中的虚拟地址
    u32 vaddr = phdr->p_vaddr;
    // 获取内存镜像中该段的大小
    u32 memSize = phdr->p_memsz;
    // 获取文件镜像中该段的大小
    u32 fileSize = phdr->p_filesz;

    // 设置段的权限
    bool canRead = false;
    bool canWrite = false;
    bool canExecute = false;
    getSegmentFlag(phdr->p_flags, canRead, canWrite, canExecute);

    // 将文件的读取起始位置设置为当前段的第一个字节
    elfFile.seekg(offset, std::ifstream::beg);
    // 读取当前段的字节数组
    char buf[fileSize];
    elfFile.read(buf, fileSize);
    // 将当前段添加内存中
    std::shared_ptr<Segment> segment = std::make_shared<Segment>(index, vaddr, memSize, reinterpret_cast<u8 *>(buf),
                                                                 fileSize, canRead, canWrite, canExecute);
    memory->addSegment(segment);
    segment->printSegmentInfo();
}

void ELF32Loader::getSegmentFlag(u32 flag, bool &canRead, bool &canWrite, bool &canExecute) {
    canRead = flag & PF_R;
    canWrite = flag & PF_W;
    canExecute = flag & PF_X;
}




